package com.gitee.wsl.security.encrypt.sample

import com.gitee.wsl.math.determinant
import com.gitee.wsl.math.invertModMatrix
import com.gitee.wsl.math.multMod
import com.gitee.wsl.math.reshape
import com.gitee.wsl.security.encrypt.Encrypt
import com.gitee.wsl.security.encrypt.StringToStringEncrypt
import kotlin.math.sqrt

/** ported from https://github.com/akourk/HillCipher/blob/master/Main.java */
class Hills(val key: String,val table: String = TABLE_A_Z,val fromZero: Boolean = true):StringToStringEncrypt{
    override fun decrypt(src: String): String {
        val invKey = parseKey(key).invertModMatrix(table.length)
        return src.padEnd(invKey.size, 'x').chunked(invKey.size).joinToString("") {
            invKey
                .multMod(it.alphabetIndexNum(fromZero = fromZero).toIntArray(), 26)
                .alphabetIndexDecodeNum(table, fromZero = fromZero)
                .lowercase()
        }
    }

    override fun encrypt(src: String): String {
        val keyMatrix = parseKey(key)
        return src.padEnd(keyMatrix.size, 'x').chunked(keyMatrix.size).joinToString("") {
            keyMatrix
                .multMod(it.alphabetIndexNum(fromZero = fromZero).toIntArray(), table.length)
                .alphabetIndexDecodeNum(table, fromZero = fromZero)
                .lowercase()
        }
    }

    private fun parseKey(key: String): Array<IntArray> {
        val keyMatrix =
            if (key.contains("\\d+".toRegex())) {
                val m = key.split("\\D+".toRegex()).map { it.toInt() }
                m.reshape(sqrt(m.size.toDouble()).toInt())
            } else {
                val m = key.alphabetIndexNum()
                m.reshape(sqrt(m.size.toDouble()).toInt())
            }
        val determinant = keyMatrix.determinant(keyMatrix.size)
        require(determinant % 26 !in intArrayOf(0, 13) && determinant % 26 % 2 != 0) {
            "wrong key matrix"
        }
        return keyMatrix
    }

}

fun Encrypt.Companion.hills( key: String, table: String = TABLE_A_Z, fromZero: Boolean = true) = Hills(key, table, fromZero)


internal fun String.alphabetIndex(
    table: String = TABLE_A_Z,
    delimiter: String = " ",
    fromZero: Boolean = false
) =
    uppercase()
        .asIterable()
        .filter { it in table }
        .map { table.indexOf(it) + (1.takeUnless { fromZero } ?: 0) }
        .joinToString(delimiter)

internal fun String.alphabetIndexNum(table: String = TABLE_A_Z, fromZero: Boolean = true) =
    uppercase()
        .asIterable()
        .filter { it in table }
        .map { table.indexOf(it) + (1.takeUnless { fromZero } ?: 0) }

internal fun String.alphabetIndexDecode(table: String = TABLE_A_Z, fromZero: Boolean = false) =
    trim()
        .split("\\D+".toRegex())
        .filter { it.matches("\\d+".toRegex()) }
        .map { table[it.toInt() - (1.takeUnless { fromZero } ?: 0)] }
        .joinToString("")

internal fun IntArray.alphabetIndexDecodeNum(table: String = TABLE_A_Z, fromZero: Boolean = true) =
    map { table[(26 + it - (1.takeUnless { fromZero } ?: 0)) % 26] }.joinToString("")